package com.xbai.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageInfo;
import com.xbai.entity.Result;
import com.xbai.goods.feign.SkuFeign;
import com.xbai.goods.pojo.Sku;
import com.xbai.search.dao.SkuEsMapper;
import com.xbai.search.pojo.SkuInfo;
import com.xbai.search.service.SkuService;
import lombok.RequiredArgsConstructor;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @author jxbai
 * @desc
 * @date 2021/2/1 0001
 */
@Service
@RequiredArgsConstructor
public class SkuServiceImpl implements SkuService {

    private final SkuFeign skuFeign;

    private final SkuEsMapper skuEsMapper;

    private final ElasticsearchTemplate template;

    /**
     * 简单搜索，
     * 先实现根据关键字搜索，每次搜索的时候，除了关键字外，还有可能有品牌、分类、规格等，后台接收搜索条件使用Map接收比较合适。
     *
     * 分类统计：每次执行搜索的时候，需要显示商品分类名称，这里要显示的分类名称其实就是符合搜素条件的所有商品的分类集合
     * 1.按关键词搜索 SELECT * FROM tb_sku WHERE name LIKE '%手机%';
     * 2.根据分类名字分组查看有多少分类 SELECT category_name FROM  tb_sku WHERE name LIKE '%手机%' GROUP BY category_name;
     *
     * 品牌统计：每次执行搜索的时候，需要显示商品品牌名称，这里要显示的品牌名称其实就是符合搜素条件的所有商品的品牌集合
     * 1.按关键词搜索 SELECT * FROM tb_sku WHERE name LIKE '%手机%';
     * 2.根据品牌名字分组查看有多少品牌 SELECT brand_name FROM  tb_sku WHERE name LIKE '%手机%' GROUP BY brand_name;
     *
     * 规格统计：每次执行搜索的时候，需要显示商品规格数据，这里要显示的规格数据其实就是符合搜素条件的所有商品的规格集合
     * 1.按关键词搜索 SELECT * FROM tb_sku WHERE name LIKE '%手机%';
     * 2.根据规格分组查看有多少规格  SELECT spec FROM  tb_sku WHERE name LIKE '%手机%' GROUP BY spec;
     * 获取到的规格数据有重复，解决思路如下
     * 1.获取所有规格数据
     * 2.将所有规格数据转换成Map
     * 3.定义一个Map<String,Set>,key是规格名字，防止重复所以用Map，value是规格值，规格值有多个，所以用集合，为了防止规格重复，用Set去除重复
     * 4.循环规格的Map，将数据填充到定义的Map<String,Set>中
     *
     * 分类、品牌筛选
     *
     * 规格过滤: 需要向后台发送规格名字以及规格值，我们可以按照一定要求来发送数据，例如规格名字以特殊前缀提交到后台：spec_网络制式：电信4G、spec_显示屏尺寸：4.0-4.9英寸
     * 后台接到数据后，可以根据前缀spec_来区分是否是规格，如果以spec_xxx开始的数据则为规格数据，需要根据指定规格找信息。
     *
     * 价格区间查询:
     * 每次需要将价格传入到后台，前端传入后台的价格大概是price=0-500或者price=500-1000依次类推，最后一个是price=3000,后台可以根据-分割，如果分割得到的结果最多有2个，第1个表示x<price，第2个表示price<=y。
     *
     * 搜索分页
     * 用户页面每次会传入当前页和每页查询多少条数据，当然如果不传入每页显示多少条数据，默认查询30条即可
     *
     * 搜索排序
     * 根据价格排序、根据评价排序、根据新品排序、根据销量排序
     * 排序要想实现非常简单，只需要告知排序的域以及排序方式即可实现。
     * 价格排序：只需要根据价格高低排序即可，降序价格高->低，升序价格低->高
     * 评价排序：评价分为好评、中评、差评，可以在数据库中设计3个列，用来记录好评、中评、差评的量，每次排序的时候，好评的比例来排序，当然还要有条数限制，评价条数需要超过N条。
     * 新品排序：直接根据商品的发布时间或者更新时间排序
     * 销量排序：销量排序除了销售数量外，还应该要有时间段限制。
     * 排序实现
     * 只需要在后台接收2个参数, 前端页面传递要排序的字段(field)和要排序的类型(ASC,DESC)
     *
     * 高亮显示
     * 高亮显示是指根据商品关键字搜索商品的时候，显示的页面对关键字给定了特殊样式，让它显示更加突出
     * 高亮搜索实现步骤解析
     * 1.指定高亮域，也就是设置哪个域需要高亮显示, 设置高亮域的时候，需要指定前缀和后缀，也就是关键词用什么html标签包裹，再给该标签样式
     * 2.高亮搜索实现
     * 3.将非高亮数据替换成高亮数据
     *
     * @param searchMap 搜索条件
     * @return {@link Map}
     */
    @Override
    public Map search(Map<String, String> searchMap) {
        // 1.获取关键字的值
        String keywords = searchMap.get("keywords");
        if (StringUtils.isEmpty(keywords)) {
            //赋值给一个默认的值
            keywords = "华为";
        }
        //2.创建查询对象的构建对象
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        //3.设置查询的条件
        //设置分组条件  商品分类
        builder.addAggregation(AggregationBuilders.terms("skuCategoryGroup").field("categoryName").size(50));
        //设置分组条件  商品品牌
        builder.addAggregation(AggregationBuilders.terms("skuBrandGroup").field("brandName").size(50));
        //设置分组条件  商品的规格
        builder.addAggregation(AggregationBuilders.terms("skuSpecGroup").field("spec.keyword").size(50));

        //设置主关键字查询
//        builder.withQuery(QueryBuilders.matchQuery("name", keywords));
        //设置高亮条件 <em style="color:red">
        builder.withHighlightFields(new HighlightBuilder.Field("name"));
        builder.withHighlightBuilder(new HighlightBuilder().preTags("<em style=\"color:red\">").postTags("</em>"));
        builder.withQuery(QueryBuilders.multiMatchQuery(keywords, "name", "brandName", "categoryName"));


        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if (!StringUtils.isEmpty(searchMap.get("brand"))) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
        }
        if (!StringUtils.isEmpty(searchMap.get("category"))) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", searchMap.get("category")));
        }

        //规格过滤查询
        for (String key : searchMap.keySet()) {
            if (key.startsWith("spec_")) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
            }
        }

        //价格过滤查询
        String price = searchMap.get("price");
        if (!StringUtils.isEmpty(price)) {
            String[] split = price.split("-");
            if (!"*".equalsIgnoreCase(split[1])) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").from(split[0], true).to(split[1], true));
            } else {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
            }
        }

        //构建过滤查询
        builder.withFilter(boolQueryBuilder);

        //构建分页查询
        String pageNumStr = searchMap.get("pageNum");
        int pageNum =StringUtils.isEmpty(pageNumStr) ? 1 : Integer.parseInt(pageNumStr);
        String pageSizeStr = searchMap.get("pageSize");
        int pageSize = StringUtils.isEmpty(pageSizeStr) ? 30 : Integer.parseInt(pageSizeStr);
        builder.withPageable(PageRequest.of(pageNum - 1, pageSize));

        //构建排序查询
        String sortRule = searchMap.get("sortRule");
        String sortField = searchMap.get("sortField");
        if(!StringUtils.isEmpty(sortRule) && !StringUtils.isEmpty(sortField)) {
            //执行排序
            builder.withSort(SortBuilders.fieldSort(sortField).order("DESC".equalsIgnoreCase(sortRule) ? SortOrder.DESC : SortOrder.ASC));
        }

        //4.构建查询对象
        NativeSearchQuery query = builder.build();

        //5.执行查询
//        AggregatedPage<SkuInfo> skuPage = template.queryForPage(query, SkuInfo.class);
        AggregatedPage<SkuInfo> skuPage = template.queryForPage(query, SkuInfo.class, new SearchResultMapperImpl());

        //获取分组结果  商品分类
        StringTerms skuCategoryGroup = (StringTerms) skuPage.getAggregation("skuCategoryGroup");
        List<String> categoryList = getStringsList(skuCategoryGroup);
        //获取分组结果  商品品牌
        StringTerms skuBrandGroup = (StringTerms) skuPage.getAggregation("skuBrandGroup");
        List<String> brandList = getStringsList(skuBrandGroup);
        //获取分组结果  商品规格数据
        StringTerms skuSpecGroup = (StringTerms) skuPage.getAggregation("skuSpecGroup");
        Map<String, Set<String>> specMap = getStringSetMap(skuSpecGroup);

        //6.返回结果
        Map<String, Object> resultMap = new HashMap<>(8);
        resultMap.put("rows", skuPage.getContent());
        resultMap.put("total", skuPage.getTotalElements());
        resultMap.put("totalPages", skuPage.getTotalPages());
        resultMap.put("categoryList", categoryList);
        resultMap.put("brandList", brandList);
        resultMap.put("specMap", specMap);
        resultMap.put("pageNum",pageNum);
        resultMap.put("pageSize",pageSize);
        return resultMap;
    }

    private Map<String, Set<String>> getStringSetMap(StringTerms stringTerms) {
        Map<String, Set<String>> result = new HashMap<>();
        Set<String> list = new HashSet<>();
        if (stringTerms != null) {
            for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
                //分组的值
                String keyAsString = bucket.getKeyAsString();
                list.add(keyAsString);
            }
        }
        for (String json : list) {
            Map<String, String> map = JSON.parseObject(json, Map.class);
            for (Map.Entry<String, String> entry : map.entrySet()) {
                //规格名字
                String key = entry.getKey();
                //规格选项值
                String value = entry.getValue();
                //获取当前规格名字对应的规格数据
                Set<String> values = result.get(key);
                if (values == null) {
                    values = new HashSet<>();
                }
                //将当前规格加入到集合中
                values.add(value);
                //将数据存入到 result Map中
                result.put(key, values);

            }
        }
        return result;
    }


    private List<String> getStringsList(StringTerms stringTerms) {
        List<String> list = new ArrayList<>();
        if (stringTerms != null) {
            for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
                //分组的值
                String keyAsString = bucket.getKeyAsString();
                list.add(keyAsString);
            }
        }
        return list;
    }

    /**
     * 导入SKU数据
     * 数据导入流程:
     * 1.请求search服务,调用数据导入地址
     * 2.根据注册中心中的注册的goods服务的地址，使用Feign方式查询所有已经审核的Sku
     * 3.使用SpringData Es将查询到的Sku集合导入到ES中
     */
    @Override
    public void importSku() {
        //调用changgou-service-goods微服务
//        Result<List<Sku>> skuListResult = skuFeign.findByStatus("1");
        save(1);
    }

    private void save(int page) {
        Sku sku = new Sku();
        sku.setStatus("1");
        //调用changgou-service-goods微服务
        Result<PageInfo> result = skuFeign.findPage(sku, page, 50);
        PageInfo pageInfo = result.getData();

        //将数据转成search.Sku
        List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(pageInfo.getList()), SkuInfo.class);
        for (SkuInfo skuInfo : skuInfos) {
            //获取规格的数据  {"电视音响效果":"立体声","电视屏幕尺寸":"20英寸","尺码":"165"}
            //转成MAP  key: 规格的名称  value:规格的选项的值
            Map<String, Object> map = JSON.parseObject(skuInfo.getSpec(), Map.class);
            skuInfo.setSpecMap(map);
        }

        // 调用spring data elasticsearch的API 导入到ES中
        skuEsMapper.saveAll(skuInfos);

        int pages = pageInfo.getPages();
        if (page < pages) {
            page ++;
            save(page);
        }
    }
}
